This vignette assumes a SQL server at localhost (we use PostgreSQL), with data in OMOP Common Data Model v5.4 format in schema cdm_new_york3. The patient records shown in this example are synthetic data from Synthea(TM) Patient Generator.

library(phea)
library(dplyr)

# Connect to SQL server.
dbcon <- DBI::dbConnect(RPostgres::Postgres(),
  host = 'localhost', port = 7654, dbname = 'fort',
  user = cred$pg$user, password = cred$pg$pass)

# Call setup_phea so we can use sqlt() and sql0().
setup_phea(dbcon, 'cdm_new_york3')

In this vignette we identify:

Case A.

Case B.

Case C.

Here’s how we compute it:

In both cases, we will normalize the unit of measurement of serum creatinine to md/dL prior to computing formulas. We could also use formulas for that, and the result would be the same.

Create components

Serum creatinine

We collect SCr records from MEASUREMENT and convert the units to mg/dL.

# Serum creatinine codes used: 
# Loinc 38483-4 Creatinine [Mass/volume] in Blood, OMOP CDM concept ID 3051825
# Loinc 2160-0 Creatinine [Mass/volume] in Serum or Plasma, OMOP CDM concept ID 3016723

# "A" records: Unit is 'mg/dL'.
scr_records_a <- sqlt(measurement) |>
  filter(measurement_concept_id %in% c(3051825, 3016723) &&
    unit_source_value == 'mg/dL')

# "B" records: Unit is 'µmol/L', but we convert to 'mg/dL'.
scr_records_b <- sqlt(measurement) |>
  filter(measurement_concept_id %in% c(3051825, 3016723) &&
    unit_source_value == 'µmol/L') |>
  mutate( # Convert µmol/L to mg/dL
    value_as_number = value_as_number / 88.42,
    unit_source_value = 'mg/dL')

# Combine all available serum creatinine records.
scr_records <- union_all(scr_records_a, scr_records_b)

# Make a record source.
scr_record_source <- make_record_source(scr_records,
  ts = measurement_datetime,
  pid = person_id)

Glomerular filtration rate

We collect GFR records from MEASUREMENT.

# GFR codes used:
# Loinc 77147-7 Glomerular filtration rate/1.73 sq M.predicted [Volume Rate/Area] in Serum, Plasma or Blood by Creatinine-based formula (MDRD)
# OMOP CDM concept ID 46236952 
gfr_records <- sqlt(measurement) |>
  filter(measurement_concept_id == 46236952)

gfr_record_source <- make_record_source(gfr_records,
    ts = measurement_datetime,
    pid = person_id)

Calculate the phenotype

Formula scr_case_a contains the logic of case A (difference ≥0.3 mg/dL), scr_case_b contains case B (ratio ≥1.5), and gfr_case_c contains case C (two GFRs under 60 within 3-5 months).

Notice we use 48 days instead of 48 hours, and 7 months instead of 7 days. This is just to make a visually meaningful example out of the limited data that is produced by Synthea(TM). Those time intervals can be changed to “48 hours” and “7 days” to produce the real phenotype.

scr_change <- calculate_formula(
  components = list(
    # Current SCr
    scr = make_component(scr_record_source),
    
    # Minimum value within 48-hour window
    scr_48h_min = make_component(scr_record_source,
      window = '48 days', .delay_fn = 'min'),
    
    # Minimum value within 7-day window
    scr_7d_min = make_component(scr_record_source,
      window = '7 months', .delay_fn = 'min'),
    
    # Current glomerular filtration rate (GFR)
    gfr = make_component(gfr_record_source),
    
    # Glomerular filtration rate 3 to 5 months older than phenotype date
    gfr_prior = make_component(gfr_record_source,
      delay = '3 months', window = '5 months', .delay_fn = 'min')),
    
  fml = list(
    scr_case_a = 'scr_value_as_number - scr_48h_min_value_as_number >= 0.3',
    
    scr_case_b = 'scr_value_as_number / scr_7d_min_value_as_number >= 1.5',
    
    gfr_case_c = 'gfr_value_as_number < 60 AND gfr_prior_value_as_number < 60'),
  
  export = c(
    'scr_measurement_datetime',
    'scr_48h_min_measurement_datetime',
    'scr_7d_min_measurement_datetime',
    'gfr_measurement_datetime',
    'gfr_prior_measurement_datetime'),
  
  .cascaded = FALSE, # Because we don't need to use results of prior formulas inside other formulas.
)

Let us take a small peek at 15 rows from the phenotype results.

head_shot(scr_change, 15) |>
  kable()
row_id pid ts window scr_value_as_number scr_48h_min_value_as_number scr_7d_min_value_as_number gfr_value_as_number gfr_prior_value_as_number scr_measurement_datetime scr_48h_min_measurement_datetime scr_7d_min_measurement_datetime gfr_measurement_datetime gfr_prior_measurement_datetime scr_case_a scr_case_b gfr_case_c
1 1 2014-03-07 00:00:00 1.4 1.4 1.4 124.5 NA 2014-03-07 2014-03-07 2014-03-07 2014-03-07 NA FALSE FALSE FALSE
2 1 2016-03-11 00:00:00 1.3 1.3 1.3 120.9 NA 2016-03-11 2016-03-11 2016-03-11 2016-03-11 NA FALSE FALSE FALSE
3 1 2018-03-16 00:00:00 1.3 1.3 1.3 151.7 NA 2018-03-16 2018-03-16 2018-03-16 2018-03-16 NA FALSE FALSE FALSE
12 1 2020-03-20 00:00:00 1.3 1.3 1.3 107.3 NA 2020-03-20 2020-03-20 2020-03-20 2020-03-20 NA FALSE FALSE FALSE
5 1 2022-02-11 00:00:00 1.8 1.5 1.5 132.3 NA 2022-02-11 2022-02-11 2022-02-11 2022-02-11 NA TRUE FALSE FALSE
16 1 2022-03-25 42 days 1.5 1.5 1.5 105.7 NA 2022-03-25 2022-02-11 2022-02-11 2022-03-25 NA FALSE FALSE FALSE
17 3 2004-11-26 00:00:00 1.5 1.5 1.5 112.0 NA 2004-11-26 2004-11-26 2004-11-26 2004-11-26 NA FALSE FALSE FALSE
47 3 2005-12-02 00:00:00 1.5 1.5 1.5 114.0 NA 2005-12-02 2005-12-02 2005-12-02 2005-12-02 NA FALSE FALSE FALSE
19 3 2006-12-08 00:00:00 1.5 1.5 1.5 111.9 NA 2006-12-08 2006-12-08 2006-12-08 2006-12-08 NA FALSE FALSE FALSE
49 3 2007-12-14 00:00:00 1.4 1.4 1.4 131.9 NA 2007-12-14 2007-12-14 2007-12-14 2007-12-14 NA FALSE FALSE FALSE
50 3 2008-12-19 00:00:00 1.3 1.3 1.3 147.5 NA 2008-12-19 2008-12-19 2008-12-19 2008-12-19 NA FALSE FALSE FALSE
22 3 2009-12-25 00:00:00 1.3 1.3 1.3 149.6 NA 2009-12-25 2009-12-25 2009-12-25 2009-12-25 NA FALSE FALSE FALSE
23 3 2010-12-31 00:00:00 1.3 1.3 1.3 132.3 NA 2010-12-31 2010-12-31 2010-12-31 2010-12-31 NA FALSE FALSE FALSE
24 3 2011-04-15 105 days 1.4 1.4 1.3 142.0 132.3 2011-04-15 2011-04-15 2010-12-31 2011-04-15 2010-12-31 FALSE FALSE FALSE
54 3 2012-01-06 371 days 1.5 1.5 1.5 91.6 132.3 2012-01-06 2012-01-06 2012-01-06 2012-01-06 2010-12-31 FALSE FALSE FALSE

Plot the phenotype for a random patient

For demonstration purposes, let us pick an “interesting” patient. By “interesting” I just mean a patient who at different times was TRUE and FALSE in each of the three criteria. A patient with variability in their timeline.

# Find an "interesting" case to plot: patients with each criteria at times TRUE, at times FALSE
patients <- scr_change |>
  group_by(pid) |>
  summarise(
    n_03 = n_distinct(scr_case_a),
    n_15 = n_distinct(scr_case_b),
    n_gfr = n_distinct(gfr_case_c)) |>
  mutate(sort_variable = n_03 + n_15 + n_gfr) |>
  arrange(desc(sort_variable)) |>
  head(20) |>
  select(pid) |>
  pull()

random_patient <- sample(patients, 1)

message('Sampled patient: ', random_patient)
#> Sampled patient: 60

Then we plot all data for the chosen patient (pid = 60).

scr_change |>
  select(-ends_with('datetime')) |>
  phea_plot(random_patient)
#> Collecting lazy table, done. (turn this message off with `verbose = FALSE`)

At the end of this report I include a large table with all the data for this patient, directly from the record sources, for maximum verification.

Obtain the SQL query that computes the phenotype

To see the SQL query underlying the phenotype, use helper function code_shot(), or dbplyr::sql_render(), or the .clip_sql option in calculate_formula().

code_shot(scr_change)
SELECT
  "row_id",
  "pid",
  "ts",
  "window",
  "scr_value_as_number",
  "scr_48h_min_value_as_number",
  "scr_7d_min_value_as_number",
  "gfr_value_as_number",
  "gfr_prior_value_as_number",
  "scr_measurement_datetime",
  "scr_48h_min_measurement_datetime",
  "scr_7d_min_measurement_datetime",
  "gfr_measurement_datetime",
  "gfr_prior_measurement_datetime",
  scr_value_as_number - scr_48h_min_value_as_number >= 0.3 AS "scr_case_a",
  scr_value_as_number / scr_7d_min_value_as_number >= 1.5 AS "scr_case_b",
  gfr_value_as_number < 60 AND gfr_prior_value_as_number < 60 AS "gfr_case_c"
FROM (
  SELECT
    *,
    "ts" - least(scr_ts, scr_48h_min_ts, scr_7d_min_ts, gfr_ts, gfr_prior_ts) AS "window",
    last_value(row_id) over (partition by "pid", "ts") AS "ts_row"
  FROM (
    SELECT
      "row_id",
      "pid",
      "ts",
      MAX("scr_measurement_datetime") OVER (PARTITION BY "pid", "..dbplyr_partion_1") AS "scr_measurement_datetime",
      MAX("scr_value_as_number") OVER (PARTITION BY "pid", "..dbplyr_partion_2") AS "scr_value_as_number",
      MAX("scr_ts") OVER (PARTITION BY "pid", "..dbplyr_partion_3") AS "scr_ts",
      MAX("scr_48h_min_measurement_datetime") OVER (PARTITION BY "pid", "..dbplyr_partion_4") AS "scr_48h_min_measurement_datetime",
      MAX("scr_48h_min_value_as_number") OVER (PARTITION BY "pid", "..dbplyr_partion_5") AS "scr_48h_min_value_as_number",
      MAX("scr_48h_min_ts") OVER (PARTITION BY "pid", "..dbplyr_partion_6") AS "scr_48h_min_ts",
      MAX("scr_7d_min_measurement_datetime") OVER (PARTITION BY "pid", "..dbplyr_partion_7") AS "scr_7d_min_measurement_datetime",
      MAX("scr_7d_min_value_as_number") OVER (PARTITION BY "pid", "..dbplyr_partion_8") AS "scr_7d_min_value_as_number",
      MAX("scr_7d_min_ts") OVER (PARTITION BY "pid", "..dbplyr_partion_9") AS "scr_7d_min_ts",
      MAX("gfr_measurement_datetime") OVER (PARTITION BY "pid", "..dbplyr_partion_10") AS "gfr_measurement_datetime",
      MAX("gfr_value_as_number") OVER (PARTITION BY "pid", "..dbplyr_partion_11") AS "gfr_value_as_number",
      MAX("gfr_ts") OVER (PARTITION BY "pid", "..dbplyr_partion_12") AS "gfr_ts",
      MAX("gfr_prior_measurement_datetime") OVER (PARTITION BY "pid", "..dbplyr_partion_13") AS "gfr_prior_measurement_datetime",
      MAX("gfr_prior_value_as_number") OVER (PARTITION BY "pid", "..dbplyr_partion_14") AS "gfr_prior_value_as_number",
      MAX("gfr_prior_ts") OVER (PARTITION BY "pid", "..dbplyr_partion_15") AS "gfr_prior_ts"
    FROM (
      SELECT
        *,
        SUM(CASE WHEN (("scr_measurement_datetime" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_1",
        SUM(CASE WHEN (("scr_value_as_number" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_2",
        SUM(CASE WHEN (("scr_ts" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_3",
        SUM(CASE WHEN (("scr_48h_min_measurement_datetime" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_4",
        SUM(CASE WHEN (("scr_48h_min_value_as_number" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_5",
        SUM(CASE WHEN (("scr_48h_min_ts" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_6",
        SUM(CASE WHEN (("scr_7d_min_measurement_datetime" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_7",
        SUM(CASE WHEN (("scr_7d_min_value_as_number" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_8",
        SUM(CASE WHEN (("scr_7d_min_ts" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_9",
        SUM(CASE WHEN (("gfr_measurement_datetime" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_10",
        SUM(CASE WHEN (("gfr_value_as_number" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_11",
        SUM(CASE WHEN (("gfr_ts" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_12",
        SUM(CASE WHEN (("gfr_prior_measurement_datetime" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_13",
        SUM(CASE WHEN (("gfr_prior_value_as_number" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_14",
        SUM(CASE WHEN (("gfr_prior_ts" IS NULL)) THEN 0 ELSE 1 END) OVER (PARTITION BY "pid" ORDER BY "pid", "ts" ROWS UNBOUNDED PRECEDING) AS "..dbplyr_partion_15"
      FROM (
        SELECT
          row_number() over () AS "row_id",
          "pid",
          "ts",
          last_value(case when "name" = 'yb5eujwgr6ln' then "measurement_datetime" else null end) over (partition by "pid", "name" order by "ts" rows between unbounded preceding and 0 preceding) AS "scr_measurement_datetime",
          last_value(case when "name" = 'yb5eujwgr6ln' then "value_as_number" else null end) over (partition by "pid", "name" order by "ts" rows between unbounded preceding and 0 preceding) AS "scr_value_as_number",
          last_value(case when "name" = 'yb5eujwgr6ln' then "ts" else null end) over (partition by "pid", "name" order by "ts" rows between unbounded preceding and 0 preceding) AS "scr_ts",
          min(case when "name" = 'yb5eujwgr6ln' then "measurement_datetime" else null end) over (partition by "pid", "name" order by "ts" range between '48 days'::interval preceding and '0 days'::interval preceding) AS "scr_48h_min_measurement_datetime",
          min(case when "name" = 'yb5eujwgr6ln' then "value_as_number" else null end) over (partition by "pid", "name" order by "ts" range between '48 days'::interval preceding and '0 days'::interval preceding) AS "scr_48h_min_value_as_number",
          min(case when "name" = 'yb5eujwgr6ln' then "ts" else null end) over (partition by "pid", "name" order by "ts" range between '48 days'::interval preceding and '0 days'::interval preceding) AS "scr_48h_min_ts",
          min(case when "name" = 'yb5eujwgr6ln' then "measurement_datetime" else null end) over (partition by "pid", "name" order by "ts" range between '7 months'::interval preceding and '0 days'::interval preceding) AS "scr_7d_min_measurement_datetime",
          min(case when "name" = 'yb5eujwgr6ln' then "value_as_number" else null end) over (partition by "pid", "name" order by "ts" range between '7 months'::interval preceding and '0 days'::interval preceding) AS "scr_7d_min_value_as_number",
          min(case when "name" = 'yb5eujwgr6ln' then "ts" else null end) over (partition by "pid", "name" order by "ts" range between '7 months'::interval preceding and '0 days'::interval preceding) AS "scr_7d_min_ts",
          last_value(case when "name" = 'pd3zki5omx6q' then "measurement_datetime" else null end) over (partition by "pid", "name" order by "ts" rows between unbounded preceding and 0 preceding) AS "gfr_measurement_datetime",
          last_value(case when "name" = 'pd3zki5omx6q' then "value_as_number" else null end) over (partition by "pid", "name" order by "ts" rows between unbounded preceding and 0 preceding) AS "gfr_value_as_number",
          last_value(case when "name" = 'pd3zki5omx6q' then "ts" else null end) over (partition by "pid", "name" order by "ts" rows between unbounded preceding and 0 preceding) AS "gfr_ts",
          min(case when "name" = 'pd3zki5omx6q' then "measurement_datetime" else null end) over (partition by "pid", "name" order by "ts" range between '5 months'::interval preceding and '3 months'::interval preceding) AS "gfr_prior_measurement_datetime",
          min(case when "name" = 'pd3zki5omx6q' then "value_as_number" else null end) over (partition by "pid", "name" order by "ts" range between '5 months'::interval preceding and '3 months'::interval preceding) AS "gfr_prior_value_as_number",
          min(case when "name" = 'pd3zki5omx6q' then "ts" else null end) over (partition by "pid", "name" order by "ts" range between '5 months'::interval preceding and '3 months'::interval preceding) AS "gfr_prior_ts"
        FROM (
          (
            SELECT
              'yb5eujwgr6ln' AS "name",
              "person_id" AS "pid",
              "measurement_datetime" AS "ts",
              "measurement_datetime",
              "value_as_number"
            FROM (
              (
                SELECT *
                FROM "cdm_new_york3"."measurement"
                WHERE ("measurement_concept_id" IN (3051825.0, 3016723.0) AND "unit_source_value" = 'mg/dL')
              )
              UNION ALL
              (
                SELECT
                  "measurement_id",
                  "person_id",
                  "measurement_concept_id",
                  "measurement_date",
                  "measurement_datetime",
                  "measurement_time",
                  "measurement_type_concept_id",
                  "operator_concept_id",
                  "value_as_number" / 88.42 AS "value_as_number",
                  "value_as_concept_id",
                  "unit_concept_id",
                  "range_low",
                  "range_high",
                  "provider_id",
                  "visit_occurrence_id",
                  "visit_detail_id",
                  "measurement_source_value",
                  "measurement_source_concept_id",
                  'mg/dL' AS "unit_source_value",
                  "unit_source_concept_id",
                  "value_source_value",
                  "measurement_event_id",
                  "meas_event_field_concept_id"
                FROM "cdm_new_york3"."measurement"
                WHERE ("measurement_concept_id" IN (3051825.0, 3016723.0) AND "unit_source_value" = 'µmol/L')
              )
            ) "q01"
          )
          UNION ALL
          (
            SELECT
              'pd3zki5omx6q' AS "name",
              "person_id" AS "pid",
              "measurement_datetime" AS "ts",
              "measurement_datetime",
              "value_as_number"
            FROM "cdm_new_york3"."measurement"
            WHERE ("measurement_concept_id" = 46236952.0)
          )
        ) "q02"
      ) "q03"
    ) "q04"
  ) "q05"
) "q06"
WHERE ("row_id" = "ts_row")

See the raw data that went into calculate_formula()

Sometimes the best way to check a result is to manually, painstakingly go over the data. Below I print all rows from the record sources for the patient that was plotted above.

Serum creatinine records

scr_records |>
  filter(person_id == random_patient) |>
  collect() |>
  arrange(measurement_datetime) |>
  select(measurement_id, person_id, measurement_datetime, value_as_number, unit_source_value) |>
  kable()
measurement_id person_id measurement_datetime value_as_number unit_source_value
24267 60 2007-09-09 1.3 mg/dL
25041 60 2008-09-14 1.3 mg/dL
24902 60 2009-09-20 1.3 mg/dL
24751 60 2010-09-26 1.2 mg/dL
24089 60 2011-10-02 1.7 mg/dL
24121 60 2011-10-02 1.5 mg/dL
24369 60 2012-10-07 1.5 mg/dL
24402 60 2012-10-07 1.5 mg/dL
24768 60 2013-10-13 1.8 mg/dL
24150 60 2014-06-22 1.3 mg/dL
24210 60 2014-10-19 1.4 mg/dL
24608 60 2015-09-06 1.3 mg/dL
24641 60 2015-10-25 1.7 mg/dL
24503 60 2016-02-21 1.4 mg/dL
24655 60 2016-09-25 1.2 mg/dL
24320 60 2016-10-30 1.5 mg/dL
24849 60 2017-08-06 2.7 mg/dL
24689 60 2017-11-05 2.8 mg/dL
24870 60 2018-01-28 6.5 mg/dL
24808 60 2018-04-29 6.7 mg/dL
24732 60 2018-08-26 6.6 mg/dL
24593 60 2018-09-30 4.2 mg/dL
24411 60 2018-10-28 4.7 mg/dL
24916 60 2018-11-11 4.4 mg/dL
24473 60 2019-03-24 4.1 mg/dL
24194 60 2019-05-26 4.2 mg/dL
24932 60 2019-07-21 4.0 mg/dL
24243 60 2019-09-22 4.6 mg/dL
24522 60 2019-11-17 4.9 mg/dL
24437 60 2020-01-19 6.8 mg/dL
24561 60 2020-05-17 4.0 mg/dL
24538 60 2020-10-18 4.8 mg/dL
24978 60 2020-11-22 5.5 mg/dL
24308 60 2021-01-17 4.7 mg/dL
24955 60 2021-11-28 4.6 mg/dL
24139 60 2022-04-10 4.9 mg/dL
25015 60 2022-05-08 4.0 mg/dL
24817 60 2022-07-10 4.8 mg/dL

Glomerular filtration rate records

gfr_records |>
  filter(person_id == random_patient) |>
  collect() |>
  arrange(measurement_datetime) |>
  select(measurement_id, person_id, measurement_datetime, value_as_number, unit_source_value) |>
  kable()
measurement_id person_id measurement_datetime value_as_number unit_source_value
24890 60 2009-09-20 134.4 mL/min/{1.73_m2}
24766 60 2010-09-26 149.9 mL/min/{1.73_m2}
24085 60 2011-10-02 77.8 mL/min/{1.73_m2}
24104 60 2011-10-02 106.2 mL/min
24396 60 2012-10-07 141.1 mL/min
24406 60 2012-10-07 76.5 mL/min/{1.73_m2}
24789 60 2013-10-13 62.9 mL/min/{1.73_m2}
24168 60 2014-06-22 83.4 mL/min/{1.73_m2}
24221 60 2014-10-19 77.6 mL/min/{1.73_m2}
24619 60 2015-09-06 83.9 mL/min/{1.73_m2}
24639 60 2015-10-25 63.9 mL/min/{1.73_m2}
24492 60 2016-02-21 80.9 mL/min/{1.73_m2}
24665 60 2016-09-25 89.6 mL/min/{1.73_m2}
24334 60 2016-10-30 73.0 mL/min/{1.73_m2}
24856 60 2017-08-06 40.5 mL/min/{1.73_m2}
24710 60 2017-11-05 39.4 mL/min/{1.73_m2}
24874 60 2018-01-28 16.7 mL/min/{1.73_m2}
24797 60 2018-04-29 16.1 mL/min/{1.73_m2}
24742 60 2018-08-26 16.4 mL/min/{1.73_m2}
24588 60 2018-09-30 25.7 mL/min/{1.73_m2}
24432 60 2018-10-28 22.8 mL/min/{1.73_m2}
24927 60 2018-11-11 24.7 mL/min/{1.73_m2}
24459 60 2019-03-24 26.2 mL/min/{1.73_m2}
24182 60 2019-05-26 25.1 mL/min/{1.73_m2}
24945 60 2019-07-21 26.6 mL/min/{1.73_m2}
24238 60 2019-09-22 22.9 mL/min/{1.73_m2}
24510 60 2019-11-17 21.6 mL/min/{1.73_m2}
24449 60 2020-01-19 15.7 mL/min/{1.73_m2}
24563 60 2020-05-17 26.6 mL/min/{1.73_m2}
24550 60 2020-10-18 21.8 mL/min/{1.73_m2}
24993 60 2020-11-22 19.2 mL/min/{1.73_m2}
24314 60 2021-01-17 22.2 mL/min/{1.73_m2}
24966 60 2021-11-28 22.8 mL/min/{1.73_m2}
24128 60 2022-04-10 21.0 mL/min/{1.73_m2}
25017 60 2022-05-08 25.8 mL/min/{1.73_m2}
24821 60 2022-07-10 21.5 mL/min/{1.73_m2}

Produce events for Atlas

One approach to use Phea’s results inside Atlas is to produce novel records (e.g. new rows in OBSERVATION or MEASUREMENT) using custom concept IDs, then ETL those back into the dataset.

Below I illustrate how to do it with case A, assuming the new records will go into MEASUREMENT. Notice I put the difference inside value_as_number, just in case.

case_a_custom_concept_id <- 2000000001
# Case A: SCr increase by >=0.3 over 48 hours
data_for_etl <- scr_change |>
  filter(scr_case_a) |> # Keep only rows where case A was TRUE
  transmute(
    measurement_datetime = ts,
    person_id = pid,
    value_as_number = scr_value_as_number - scr_48h_min_value_as_number,
    measurement_concept_id = case_a_custom_concept_id)

head_shot(data_for_etl) |>
  kable()
measurement_datetime person_id value_as_number measurement_concept_id
2022-02-11 1 0.3 2000000001
2016-11-27 19 1.6 2000000001
2018-12-16 19 0.8 2000000001
2019-11-03 19 1.0 2000000001
2020-11-22 19 0.6 2000000001
2021-11-14 19 0.8 2000000001
2021-02-21 30 0.4 2000000001
2021-02-23 30 0.3 2000000001
2021-02-24 30 0.3 2000000001
2021-02-25 30 0.3 2000000001

Author contact

Fabrício Kury – Please be always welcome to reach me at .